"""
    Fixme: This version does not ensure the correct behavior under socket failure;
"""
import socket
import sys
import threading
import time

from PyQt5.QtWidgets import QWidget, QApplication, QButtonGroup
from PyQt5.QtCore import QTimer, QTimerEvent
from PassengerLogIn import Ui_Form as PassengerLogInUi
from PassengerControllerui import Ui_Form as PassengerControlUi
from WarningDialog import Ui_Form as ErrorDialog
import struct
import _thread


class DialogWidget(QWidget):
    def __init__(self, client):
        assert isinstance(client, Client)
        self.client = client
        super().__init__()

    def closeEvent(self, evt) -> None:
        assert isinstance(self.client.panel, ClientControlPanel)
        # Make the main window enable again
        self.client.login.loginWindow.setEnabled(True)
        self.client.panel.panelWindow.setEnabled(True)


class PanelWidget(QWidget):
    def __init__(self, client):
        assert isinstance(client, Client)
        self.client = client
        super().__init__()

    def timerEvent(self, evt: QTimerEvent) -> None:
        self.client.panel.status_request()

    def closeEvent(self, evt) -> None:
        assert isinstance(self.client.panel, ClientControlPanel)
        self.client.panel.panelWindowContent.acPowerOff.setChecked(True)
        self.client.panel.disconnect()
        # When log out, clear the input of LogIn-Interface and automatically close the AC
        self.client.login.input_reset()
        self.client.login.loginWindow.show()


class ClientDialog:
    def __init__(self, client):
        assert isinstance(client, Client)
        self.client = client
        self.errorDialogWindow = DialogWidget(client)
        self.errorDialogWindowContent = ErrorDialog()
        self.errorDialogWindowContent.setupUi(self.errorDialogWindow)
        self.errorDialogWindowContent.pushButton.clicked.connect(self.errorDialogWindow.close)

    def show_dialog(self, caution_str):
        assert isinstance(caution_str, str)
        self.errorDialogWindowContent.label.setText(
            "<html><head/><body><p align=\"center\"><span style=\" font-weight:600;\">" + caution_str + "</span></p></body></html>")
        self.errorDialogWindow.show()


class ClientLogIn:
    def __init__(self, client):
        assert isinstance(client, Client)
        self.client = client
        # Todo: May change the way to load ID-Password
        self.PasswdList = ['66666666', '88888888']

        self.loginWindow = QWidget()
        self.loginWindowContent = PassengerLogInUi()
        self.loginWindowContent.setupUi(self.loginWindow)

        self.loginWindowContent.logInButton.clicked.connect(self.client.login_request)

    def login_request(self):
        try:
            self.client.roomID = int(self.loginWindowContent.idEdit.text())
            if not 0 < self.client.roomID < 1000:
                raise ValueError
        except ValueError as e:
            return False
        login_passwd = self.loginWindowContent.passwdEdit.text()
        if login_passwd in self.PasswdList:
            return True
        else:
            return False

    def show_login(self):
        self.loginWindow.show()

    def input_reset(self):
        self.loginWindowContent.idEdit.setText('')
        self.loginWindowContent.passwdEdit.setText('')


class ClientControlPanel:
    def __init__(self, client):
        assert isinstance(client, Client)
        self.client = client
        self.panelWindow = PanelWidget(client)
        self.panelWindowContent = PassengerControlUi()
        self.panelWindowContent.setupUi(self.panelWindow)

        self.socketToServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.default_settings = None
        self.fanFee = None
        self.timer_status = None

        self.defaultTemperature = int(sys.argv[1])
        self.roomTemperature = -1
        self.service_status = -1
        self.flag = False

        # Connecting signals and slots below
        self.panelWindowContent.logOutButton.clicked.connect(self.panelWindow.close)
        self.panelWindowContent.submitButton.clicked.connect(self.settings_submit)
        # Once the fan-speed changed, matched fee-rate displayed simultaneously
        self.panelWindowContent.fanLow.clicked.connect(self.change_fan_fee_display)
        self.panelWindowContent.fanMedium.clicked.connect(self.change_fan_fee_display)
        self.panelWindowContent.fanHigh.clicked.connect(self.change_fan_fee_display)
        # The same for target-temperature
        self.panelWindowContent.temperatureDial.valueChanged.connect(self.change_target_temperature_display)
        self.panelWindowContent.resetButton.clicked.connect(self.reset_settings)
        # By default, AC is off
        self.panelWindowContent.acPowerOff.setChecked(True)
        self.tempBackThread = threading.Thread(target=self.temp_back, args=())

        fan_group = QButtonGroup(self.panelWindow)
        fan_group.addButton(self.panelWindowContent.fanLow)
        fan_group.addButton(self.panelWindowContent.fanMedium)
        fan_group.addButton(self.panelWindowContent.fanHigh)

        mode_group = QButtonGroup(self.panelWindow)
        mode_group.addButton(self.panelWindowContent.warmMode)
        mode_group.addButton(self.panelWindowContent.coldMode)

    def temp_back(self):
        while self.flag:
            if self.service_status != 1:
                print("Temperature backing!", self.defaultTemperature, self.roomTemperature)
                self.roomTemperature += 0.5 / 20 if self.defaultTemperature > self.roomTemperature \
                    else -0.5 / 20 if self.defaultTemperature < self.roomTemperature else 0
                print("Temperature back to", self.roomTemperature)
                self.socketToServer.send(struct.pack('!if', 4, self.roomTemperature))
                time.sleep(15)
        pass

    def set_fan_speed(self, fan_speed) -> None:
        if fan_speed == 1:
            self.panelWindowContent.fanLow.click()
        elif fan_speed == 2:
            self.panelWindowContent.fanMedium.click()
        elif fan_speed == 3:
            self.panelWindowContent.fanHigh.click()
        else:
            print("Wrong FanSpeed set!")
            sys.exit(1)

    def get_fan_speed(self) -> int:
        if self.panelWindowContent.fanLow.isChecked():
            return 1
        elif self.panelWindowContent.fanMedium.isChecked():
            return 2
        else:
            return 3

    def set_warm_cold(self, warm_cold) -> None:
        if warm_cold == 0:
            self.panelWindowContent.warmMode.click()
            self.panelWindowContent.coldMode.setCheckable(False)
        elif warm_cold == 1:
            self.panelWindowContent.coldMode.click()
            self.panelWindowContent.warmMode.setCheckable(False)
        else:
            print("Wrong Mode!")
            sys.exit(1)

    def get_warm_cold(self) -> int:
        if self.panelWindowContent.warmMode.isChecked():
            return 0
        else:
            return 1

    def socket_connect(self) -> bool:
        try:
            # Todo: Change the target address
            self.socketToServer.connect((socket.gethostname(), 8000))
        except socket.error as e:
            print(e)
            return False
        else:
            try:
                self.socketToServer.recv(1024, socket.MSG_DONTWAIT)
            except BlockingIOError as e:
                pass
            self.socketToServer.send(struct.pack('!iii', 0, self.client.roomID, self.defaultTemperature))
            ini_info = struct.unpack('!iiifffff', self.socketToServer.recv(32))
            print(ini_info)
            if ini_info[0] == -1:
                self.disconnect()
                self.client.dialog.show_dialog("Invalid Room ID!")
                return False
            else:
                default_warm_cold = ini_info[0]
                default_temperature = ini_info[1]
                default_fan_speed = ini_info[2]
                fan_low_fee = ini_info[3]
                fan_mid_fee = ini_info[4]
                fan_high_fee = ini_info[5]
                max_temperature = ini_info[6]
                min_temperature = ini_info[7]
                # Saving the default-settings got
                self.default_settings = (default_warm_cold, default_temperature, default_fan_speed)
                self.fanFee = (fan_low_fee, fan_mid_fee, fan_high_fee)
                # Do the setting
                self.panelWindowContent.temperatureDial.setMinimum(int(min_temperature))
                self.panelWindowContent.temperatureDial.setMaximum(int(max_temperature))
                self.panelWindowContent.roomID.setText(str(self.client.roomID))
                # Todo: Timer of 3-seconds interval
                self.timer_status = self.panelWindow.startTimer(15 * 1000)
                self.roomTemperature = self.defaultTemperature
                self.flag = True
                self.tempBackThread.start()
                return True

    def change_fan_fee_display(self) -> None:
        fee = None
        if self.panelWindowContent.fanLow.isChecked():
            fee = str(self.fanFee[0])[0:4]
        elif self.panelWindowContent.fanMedium.isChecked():
            fee = str(self.fanFee[1])[0:4]
        else:
            fee = str(self.fanFee[2])[0:4]
        self.panelWindowContent.fanFee.setText(fee)

    def change_target_temperature_display(self) -> None:
        self.panelWindowContent.targetTemprerature.display(str(self.panelWindowContent.temperatureDial.value()))

    def reset_settings(self) -> None:
        self.set_warm_cold(self.default_settings[0])
        self.panelWindowContent.temperatureDial.setValue(self.default_settings[1])
        self.panelWindowContent.targetTemprerature.display(str(self.default_settings[1]))
        self.set_fan_speed(self.default_settings[2])

    def settings_submit(self) -> None:
        if self.panelWindowContent.acPowerOn.isChecked():
            self.socketToServer.send(struct.pack('!ii', 0, 0))
        else:
            self.socketToServer.send(struct.pack('!ii', 5, 0))
        self.socketToServer.send(struct.pack('!ii', 1, self.get_fan_speed()))
        self.socketToServer.send(struct.pack('!ii', 2, self.panelWindowContent.temperatureDial.value()))

    def status_request(self) -> None:
        print("Trying to get current status!")
        try:
            self.socketToServer.recv(1024, socket.MSG_DONTWAIT)
        except OSError as e:
            pass
        self.socketToServer.send(struct.pack('!ii', 3, 0))
        print("Sent request_status")
        current_status = struct.unpack('!ifff', self.socketToServer.recv(16))
        self.panelWindowContent.curServiceStatus.setText(
            "STOP" if current_status[0] == 0 else "RUN" if current_status[0] == 1
            else "WAIT" if current_status[0] == 2 else "PAUSE")
        self.panelWindowContent.curTemperature.display(str(current_status[1])[0:4])
        self.roomTemperature = current_status[1]
        self.service_status = current_status[0]
        self.panelWindowContent.curFee.display(str(current_status[2])[0:4])
        print("Accumulated Fee:", current_status[3])
        self.panelWindowContent.accFee.display(str(current_status[3])[0:4])

    def disconnect(self) -> None:
        self.flag = False
        self.socketToServer.close()
        self.socketToServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        if self.timer_status is not None:
            self.panelWindow.killTimer(self.timer_status)

    def show_panel(self) -> None:
        self.panelWindow.show()


class Client:
    def __init__(self) -> None:
        self.login = ClientLogIn(self)
        self.dialog = ClientDialog(self)
        self.panel = ClientControlPanel(self)

        # Todo : Modify the room id and default temperature in Release-Version
        self.roomID = -1

    def show(self) -> None:
        self.login.loginWindow.show()

    def login_request(self) -> None:
        if self.login.login_request():
            if self.panel.socket_connect():
                self.login.loginWindow.close()
                self.panel.reset_settings()
                self.panel.show_panel()
            else:
                self.login.loginWindow.setEnabled(False)
                self.dialog.show_dialog("Fail to connect to central server or room not exists...")
        else:
            # Password Wrong
            self.login.loginWindow.setEnabled(False)
            self.dialog.show_dialog("ID or Password Wrong!")


if __name__ == '__main__':
    clientProcess = QApplication([])
    clientWindow = Client()
    clientWindow.show()
    clientProcess.exec_()
